import { isElementNode, isTextNode } from '../utils'

interface DirectiveOption {
  args:string
  expr:string
  data:object
  methods:object
}

function node2fragment(rootNode:Node) {
  const fragment = document.createDocumentFragment()

  let child: ChildNode|null = null

  while(child = rootNode.firstChild) {
    fragment.appendChild(child)
  }

  return fragment
}

function get<T extends object>(target:T, expr:string) {
  return expr.split('.').reduce((prev, key)=>{
    return Reflect.get(prev, key) as any
  }, target)
}

function set<T extends object>(target:T, expr:string, value:unknown) {}


const DirectiveUtils = {
  text(el:HTMLElement, opt:DirectiveOption) {
    el.innerText = `${get(opt.data, opt.expr)}`
  },
  
  html(el:HTMLElement, opt:DirectiveOption) {
    el.innerHTML = `${get(opt.data, opt.expr)}`
  },
  
  bind(el:HTMLElement, opt:DirectiveOption) {
    el.setAttribute(opt.args, `${get(opt.data, opt.expr)}`)
  },
  
  model(el:HTMLInputElement, opt:DirectiveOption) {
    const { data, expr } = opt
    el.value = `${get(data, expr)}`
    el.addEventListener('input', ev=>{
      const { value } = ev.target as HTMLInputElement
      // TODO ...
    })
  },
  
  on(el:HTMLElement, opt:DirectiveOption) {
    el.addEventListener(opt.args, get(opt.methods, opt.expr) as any, false)
  }
}

export class Compiler {
  constructor(el:string|HTMLElement) {
    const container = typeof el === 'string' ? document.querySelector(el)! : el
    const fragment = node2fragment(container)

    // container.cloneNode(true)

    this.compile(fragment)

    container.appendChild(fragment)
  }

  compile(root:Node) {
    root.childNodes.forEach(child=>{
      if(isElementNode(child)) {
        if(child.childNodes.length && child.childNodes.length > 0) this.compile(child)
        this.parseElement(child as HTMLElement)
      } else if(isTextNode(child)) {
        this.parseText(child as Text)
      }
    })
  }

  parseText(text:Text) {
    const pattern = /\{\{\s*(\w+)\s*\}\}/g

    text.textContent = text.textContent?.replace(pattern, (item, expr, idx)=>{
      return `${get({}, expr)}`
    })!
  }

  parseElement(el:HTMLElement) {
    for(const { name:originName, value:expr } of el.attributes) {
      let name = originName
      
      if(name.startsWith(':')) name = name.replace(':', 'v-bind:')
      else if(name.startsWith('@')) name = name.replace('@', 'v-on:')

      if(name.startsWith('v-')) {
        const [ directiveName, directiveArgs ] = name.slice(2).split(':')

        Reflect.get(DirectiveUtils, directiveName)(el, {args:directiveArgs, expr, data:{}, methods:{}})
        
        el.removeAttribute(originName)
      }
    }
  }
}
